{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 在Flask中定制响应和接入点(Hook Points)"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 自定义响应\r\n",
    "\r\n",
    "Flask 提供了三种不同的反应模式:\r\n",
    "\r\n",
    "1. 作为字符串或使用模板引擎\r\n",
    "2. 作为响应对象\r\n",
    "3. 作为表单中的元组\r\n",
    "\r\n",
    "让我们一一来看看这些模式。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 1. 将响应创建为字符串\r\n",
    "```\r\n",
    "@app.route('/books/<genre>')\r\n",
    "def books(genre):\r\n",
    "    return \"All Books in {} category\".format(genre)\r\n",
    "```\r\n",
    "\r\n",
    "到目前为止，我们已经使用此模式向客户机发送响应。当 Flask 看到我们从视图函数返回一个字符串时，它会自动将字符串转换成一个响应对象(使用 `make_response()` 方法) ，其中字符串作为响应的主体，HTTP状态码为200，内容类型的头部设置为 text/html。    \r\n",
    "\r\n",
    "大多数时候，这就是你所需要的。但是，有时候在向客户端发送响应之前，您需要设置一些额外的消息头。对于这种情况，必须使用 `make_response()` 函数创建响应。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 2. 用 make_response()创建响应\r\n",
    "\r\n",
    "make_response()的语法如下:\r\n",
    "\r\n",
    "`res_obj = make_response(res_body, status_code=200)`\r\n",
    "\r\n",
    "`res_body` 是表示响应主体的必需参数， \r\n",
    "`status_code` 状态代码是可选的 HTTP 状态码，默认值为200。\r\n",
    "\r\n",
    "下面的清单显示了如何使用 `make_response()` 函数设置附加标头。\r\n",
    "```python\r\n",
    "from flask import Flask, make_response, \r\n",
    "\r\n",
    "@app.route('/books/<genre>')\r\n",
    "def books(genre):\r\n",
    "    res = make_response(\"All Books in {} category\".format(genre))\r\n",
    "    res.headers['Content-Type'] = 'text/plain'\r\n",
    "    res.headers['Server'] = 'Foobar'\r\n",
    "    return res\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单显示了如何使用 `make_response()`  函数返回 HTTP 404错误。\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_404_handler():\r\n",
    "    return make_response(\"<h2>404 Error</h2>\", 400)\r\n",
    "```\r\n",
    "\r\n",
    "设置 cookie 是 web 应用程序中的另一个常见任务。函数 make _ response ()使得这个操作非常简单。下面的清单在客户端浏览器中设置了两个 cookie。第三个参数来设置 cookie 的过期时间(15天)\r\n",
    "```python\r\n",
    "@app.route('/cookie')\r\n",
    "def set_cookie():\r\n",
    "    res = make_response(\"Cookie setter\")\r\n",
    "    res.set_cookie(\"favorite-color\", \"skyblue\", 60*60*24*15)\r\n",
    "    res.set_cookie(\"favorite-font\", \"sans-serif\", 60*60*24*15)\r\n",
    "    return res\r\n",
    "```\r\n",
    "\r\n",
    "上面列表设置的 cookie 将持续到浏览器会话。我们可以通过传递秒数作为 `set_cookie()` 方法的第三个参数来设置 cookie 的过期时间。\r\n",
    "\r\n",
    "此清单将 cookie 的过期时间设置为15天。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 3. 使用元组创建响应\r\n",
    "\r\n",
    "创建响应的最后一个模式是使用元组，格式如下:\r\n",
    "```\r\n",
    "(response, status, headers) \r\n",
    "(response, headers) \r\n",
    "(response, status)\r\n",
    "```\r\n",
    "`response` 响应是一个字符串，表示响应的主体， `status` 状态是 HTTP状态码，它可以是整数或字符串， `headers` 头是一个包含头值的字典。\r\n",
    "\r\n",
    "此视图函数将返回 HTTP 500内部服务器错误。\r\n",
    "\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_500_handler():\r\n",
    "    return (\"<h2>500 Error</h2>\", 500)\r\n",
    "```\r\n",
    "\r\n",
    "此视图函数将返回 HTTP 500内部服务器错误。因为在创建元组时我们可以省略括号，所以上面的代码也可以这样写:\r\n",
    "\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def http_500_handler():\r\n",
    "    return \"<h2>500 Error</h2>\", 500\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单展示了如何使用元组设置头文件:\r\n",
    "\r\n",
    "```python\r\n",
    "@app.route('/')\r\n",
    "def render_markdown():\r\n",
    "    return \"## Heading\", 200, {'Content-Type': 'text/markdown'}\r\n",
    "```\r\n",
    "\r\n",
    "下面的清单使用 HTTP 302响应代码(临时重定向)将用户重定向到 HTTP://localhost:5000/login\r\n",
    "```python\r\n",
    "@app.route('/transfer')\r\n",
    "def transfer():\r\n",
    "    return \"\", 302, {'location': 'http://localhost:5000/login'}\r\n",
    "```\r\n",
    "\r\n",
    "将用户重定向到不同的页面是很常见的，所以 Flask 提供了一个名为 `redirect()` 的辅助函数(helper function)，以简化这项工作。\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask, redirect\r\n",
    "\r\n",
    "@app.route('/transfer')\r\n",
    "def transfer():\r\n",
    "    return redirect(\"http://localhost:5000/login\")\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "默认情况下，redirect ()执行 HTTP 302重定向，执行 HTTP 301重定向，将301的 HTTP状态码传递给 redirect ()函数，如下所示:\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask, redirect\r\n",
    "\r\n",
    "@app.route('/transfer')\r\n",
    "def transfer():\r\n",
    "    return redirect(\"http://localhost:5000/login\", code=301)\r\n",
    "\r\n",
    "```\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Hook Points 接入点\r\n",
    "\r\n",
    "在 web 应用程序中，在每个请求之前执行一些代码是很常见的。例如，假设我们想记录访问我们应用程序的用户的 IP 地址，或者在显示隐藏页面之前对用户进行身份验证。与其在每个视图函数中复制这样的代码，Flask 为这样的场景提供了下面的装饰器 decorators :\r\n",
    "\r\n",
    "`before_first_request`: 这个 装饰器 注册一个函数，在处理第一个请求之前执行。\r\n",
    "\r\n",
    "`before_request`:  这个 装饰器 注册一个函数，在处理请求之前执行。\r\n",
    "\r\n",
    "`after_request`: : 这个 装饰器 注册一个函数，在处理请求之后执行。如果请求处理程序中发生未处理的异常，则不会调用已注册的函数。函数必须接受一个响应对象并返回相同或新的响应。\r\n",
    "\r\n",
    "`teardown_request`: 类似于 `after_request` 装饰器 ，但是注册的函数将始终执行，不管请求处理程序是否抛出异常。\r\n",
    "\r\n",
    "请注意，如果在 `before_request` 装饰器之前注册的函数返回一个响应，那么请求处理程序将不会被调用。\r\n",
    "\r\n",
    "下面的清单演示了如何利用 Flask 中的钩子点。用下面的代码创建一个名为 hooks.py 的新文件:\r\n",
    "\r\n",
    "### flask_app/hooks.py\r\n",
    "\r\n",
    "```python\r\n",
    "from flask import Flask, request, g\r\n",
    "\r\n",
    "app = Flask(__name__)\r\n",
    "\r\n",
    "@app.before_first_request\r\n",
    "def before_first_request():\r\n",
    "    print(\"before_first_request() called\")\r\n",
    "\r\n",
    "@app.before_request\r\n",
    "def before_request():\r\n",
    "    print(\"before_request() called\")\r\n",
    "\r\n",
    "@app.after_request\r\n",
    "def after_request(response):\r\n",
    "    print(\"after_request() called\")\r\n",
    "    return response\r\n",
    "\r\n",
    "@app.route(\"/\")\r\n",
    "def index():\r\n",
    "    print(\"index() called\")\r\n",
    "    return '<p>Testings Request Hooks</p>'\r\n",
    "\r\n",
    "if __name__ == \"__main__\":\r\n",
    "    app.run(debug=True)\r\n",
    "```    \r\n",
    "\r\n",
    "启动服务器，通过访问 http://localhost:5000/服务器提出你的第一个请求。在运行服务器的 shell 的标准输出中，应该得到以下输出:\r\n",
    "\r\n",
    "```\r\n",
    "before_first_request() called\r\n",
    "before_request() called\r\n",
    "index() called\r\n",
    "after_request() called\r\n",
    "```\r\n",
    "\r\n",
    "刷新页面，这次应该在 shell 中得到以下输出。\r\n",
    "\r\n",
    "```\r\n",
    "before_request() called\r\n",
    "index() called\r\n",
    "after_request() called\r\n",
    "\r\n",
    "```\r\n",
    "\r\n",
    "这是因为第二次请求时 before_first_request() 函数未执行。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 使用 abort() 中止请求\r\n",
    "\r\n",
    "Flask 提供了一个名为 abort ()的函数，用于终止具有特定错误码(如404、500等)的请求。例如:\r\n",
    "\r\n",
    "```\r\n",
    "from flask import Flask, abort\r\n",
    "\r\n",
    "@app.route('/')\r\n",
    "def index():\r\n",
    "    abort(404)\r\n",
    "    # code after abort() execution will never be executed\r\n",
    "This view function will return a generic 404 page that looks like this:\r\n",
    "```\r\n",
    "\r\n",
    "这个视图函数将返回一个通用的404页面.\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/7/abort404_page-8328384b-a2ef-4c0b-bf57-6a33e36e00b9.png)\r\n",
    "\r\n",
    "`Abort()` 将为其他类型的错误显示类似的页面。如果要自定义错误页面，请使用下面将要讨论的 errorhandler 装饰器。\r\n"
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Custom Error Pages 自定义错误页面\r\n",
    "\r\n",
    "`errorhandler` 装饰器用于创建自定义错误页面。它只接受一个参数，即为其创建自定义错误页面的 HTTP 错误代码。使用 `errorhandler` `decorator` 打开 hooks.py 文件并为 HTTP 404和 HTTP 500错误创建自定义错误页面，如下所示(突出显示更改) :\r\n",
    "\r\n",
    "### flask_app/hooks.py\r\n",
    "\r\n",
    "```python\r\n",
    "## The errorhandler decorator\r\n",
    "@app.errorhandler(404)\r\n",
    "def http_404_handler(error):\r\n",
    "    return \"<p>HTTP 404 Error Encountered</p>\", 404\r\n",
    "\r\n",
    "@app.errorhandler(500)\r\n",
    "def http_500_handler(error):\r\n",
    "    return \"<p>HTTP 500 Error Encountered</p>\", 500\r\n",
    "\r\n",
    "@app.route(\"/test/\")\r\n",
    "def test():\r\n",
    "    # print(\"index() called\")\r\n",
    "    # return '<p>Testings Request Hooks</p>'\r\n",
    "    abort(404)\r\n",
    "```\r\n",
    "\r\n",
    "请注意，两个错误处理程序都接受一个名为 error 的参数，该参数包含有关所发生错误类型的附加信息。\r\n",
    "\r\n",
    "如果你现在访问根URL，你会得到如下回复:\r\n",
    "\r\n",
    "![img](https://overiq.com/media/uploads/2018/1/7/custom_404_page-7b9ebced-ee6e-4e87-b329-b0336a6a6e81.png)"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}